﻿using System;
using System.Collections.Generic;
using System.IO;
using Unity.Collections;
using Unity.Entities;
using Unity.Entities.Content;
using UnityEditor;
using UnityEngine;

[Serializable]
internal struct RemoteContentCatalogData
{
    [Serializable]
    public struct RemoteContentLocationData
    {
        public RemoteContentId identifier;
        public RemoteContentLocation location;
    }

    [Serializable]
    public struct RemoteContentSetData
    {
        public FixedString512Bytes Name;
        public BlobArray<int> Ids;
    }

    public BlobArray<RemoteContentLocationData> RemoteContentLocations;
    public BlobArray<RemoteContentSetData> ContentSets;
}

public class LocationIdentifier
{
    public string location;
    public string identitier;
}

public class BundleInfo
{
    public LocationIdentifier file = new LocationIdentifier();

    public List<string> objects = new List<string>();
    public List<string> dependencies = new List<string>();
}

public class ContentArchiveAnalysis : EditorWindow
{
    private string rootDirectory;
    private string tips = string.Empty;
    private LocationIdentifier catalogFile;
    private LocationIdentifier dependenciesFile;
    private List<BundleInfo> archiveBunldes = new List<BundleInfo>();
    private List<LocationIdentifier> otherFiles = new List<LocationIdentifier>();
    
    [MenuItem("Tools/ContentArchiveAnalysis")]
    private static void ShowWindow()
    {
        var window = GetWindow<ContentArchiveAnalysis>();
        window.titleContent = new GUIContent("ContentArchiveAnalysis");
        window.Show();
    }

    private void OnEnable()
    {
        catalogFile = null;
    }

    private void OnGUI()
    {
        if (catalogFile != null)
        {
            EditorGUILayout.LabelField("跟目录", rootDirectory);
            ShowInfo("Catalog文件", catalogFile.location, catalogFile.identitier, Color.red, (p) =>
            {
                tips = string.Empty;
                OpenDirectory(p);
            }, (p) =>
            {
                tips = string.Empty;
                OpenOriginalDirectory("ContentArchives/" + p);
            });
            ShowInfo("Dependencies文件", dependenciesFile.location, dependenciesFile.identitier, Color.red, (p) =>
            {
                tips = string.Empty;
                OpenDirectory(p);
            }, (p) =>
            {
                tips = string.Empty;
                OpenOriginalDirectory(p);
            });
            
            EditorGUILayout.Space();

            for (int i = 0; i < archiveBunldes.Count; ++i)
            {
                BundleInfo archiveBundle = archiveBunldes[i];
                ShowInfo($"AssetBundle文件[{i}]", archiveBundle.file.location, archiveBundle.file.identitier, Color.green, (p) =>
                {
                    tips = string.Empty;
                    OpenDirectory(p);
                }, (p) =>
                {
                    tips = string.Empty;
                    OpenOriginalDirectory(p);
                });

                EditorGUILayout.BeginHorizontal();
                GUILayout.Space(22.0f);
                EditorGUILayout.BeginVertical();
                for (int oi = 0; oi < archiveBundle.objects.Count; ++oi)
                {
                    string objectName = archiveBundle.objects[oi];
                    string guid = objectName.Substring(0, objectName.LastIndexOf(':'));
                    ShowInfo($"资源[{oi}]", objectName, AssetDatabase.GUIDToAssetPath(guid), Color.cyan, (p) =>
                    {
                        tips = $"这是AssetBundle:{archiveBundle.file.identitier}里面的一个资源{AssetDatabase.GUIDToAssetPath(guid)}";
                        Selection.activeGameObject = AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(guid));
                    },(p) =>
                    {
                        tips = $"这是AssetBundle:{archiveBundle.file.identitier}里面的一个资源{AssetDatabase.GUIDToAssetPath(guid)}";
                        Selection.activeGameObject = AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(guid));
                    });
                }
                for (int j = 0; j < archiveBundle.dependencies.Count; ++j)
                {
                    string dependency = archiveBundle.dependencies[j];
                    ShowInfo($"依赖[{j}]", dependency, "", new Color(0.5f, 0.7f, 1.0f), (p) =>
                    {
                        tips = $"这是AssetBundle:{archiveBundle.file.identitier}依赖的AssetBunld的Hash，值为：{p}";
                    });
                }
                EditorGUILayout.EndVertical();
                EditorGUILayout.EndHorizontal();
                
                EditorGUILayout.Space();
            }

            for (int i = 0; i < otherFiles.Count; ++i)
            {
                LocationIdentifier file = otherFiles[i];
                ShowInfo($"其他文件[{i}]", file.location, file.identitier, Color.magenta, (p) =>
                {
                    tips = string.Empty;
                    OpenDirectory(p);
                }, (p) =>
                {
                    tips = string.Empty;
                    OpenOriginalDirectory(p);
                });
            }
        }
        
        EditorGUILayout.HelpBox(tips, MessageType.Info);
        
        if (GUILayout.Button("分析"))
        {
            catalogFile = null;
            dependenciesFile = null;
            archiveBunldes.Clear();
            otherFiles.Clear();
            rootDirectory = EditorUtility.OpenFolderPanel("选择Content Archive的根目录", Application.dataPath, "Builds-RemoteContent");
            if (string.IsNullOrEmpty(rootDirectory))
            {
                return;
            }
            if (!BlobAssetReference<RemoteContentCatalogData>.TryRead(rootDirectory + "/catalogs.bin", 1,
                    out BlobAssetReference<RemoteContentCatalogData> bin))
            {
                return;
            }
            for (int i = 0; i < bin.Value.RemoteContentLocations.Length; ++i)
            {
                var locationData = bin.Value.RemoteContentLocations[i];

                catalogFile = new LocationIdentifier()
                {
                    location = locationData.location.Path.ToString(),
                    identitier = locationData.identifier.Name.ToString()
                };
                Analysis(rootDirectory, locationData.location.Path.ToString());
            }
        }
    }

    private void OpenDirectory(string file)
    {
        string path = rootDirectory + "/" + file;
        path = Path.GetDirectoryName(path);
        path = path?.Replace("/", "\\");
        if (!Directory.Exists(path))
        {
            Debug.LogError("No Directory: " + path);
            return;
        }
        System.Diagnostics.Process.Start("explorer.exe", path);
    }
    
    private void OpenOriginalDirectory(string file)
    {
        string path = Application.dataPath + "/../Library/EntitySceneBundles/" + file;
        path = Path.GetDirectoryName(path);
        path = path?.Replace("/", "\\");
        if (!Directory.Exists(path))
        {
            Debug.LogError("No Directory: " + path);
            return;
        }
        System.Diagnostics.Process.Start("explorer.exe", path);
    }

    private void ShowInfo(string lable, string col1, string col2, Color color, Action<string> click1 = null, Action<string> click2 = null)
    {
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.LabelField(lable, GUILayout.Width(128.0f));
        
        Color bc = GUI.backgroundColor;
        GUI.backgroundColor = color;

        if (string.IsNullOrEmpty(col1))
        {
            GUILayout.Label("", GUILayout.Width(280.0f));
        }
        else if(GUILayout.Button(col1, GUILayout.Width(280.0f)))
        {
            click1?.Invoke(col1);
        }

        if (string.IsNullOrEmpty(col2))
        {
            GUILayout.Label("");
        }
        else if (GUILayout.Button(col2))
        {
            click2?.Invoke(col2);
        }

        GUI.backgroundColor = bc;
        
        EditorGUILayout.EndHorizontal();
    }

    private bool IsDependenciesTextFile(string identifier)
    {
        return identifier == "ContentArchives/archive_dependencies.txt";
    }

    private void Analysis(string rootDirectory, string catalogPath)
    {
        if (!BlobAssetReference<RemoteContentCatalogData>.TryRead(Path.Combine(rootDirectory, catalogPath), 1,
                out BlobAssetReference<RemoteContentCatalogData> catalog))
        {
            return;
        }
        for (int i = 0; i < catalog.Value.RemoteContentLocations.Length; ++i)
        {
            var locationData = catalog.Value.RemoteContentLocations[i];
            if (!IsDependenciesTextFile(locationData.identifier.Name.ToString()))
            {
                continue;
            }
            dependenciesFile = new LocationIdentifier()
            {
                location = locationData.location.Path.ToString(),
                identitier = locationData.identifier.Name.ToString()
            };
            AnalysisDependencies(rootDirectory);
            break;
        }

        for (int i = 0; i < catalog.Value.RemoteContentLocations.Length; ++i)
        {
            var locationData = catalog.Value.RemoteContentLocations[i];
            if (IsDependenciesTextFile(locationData.identifier.Name.ToString()))
            {
                continue;
            }
            string identitier = locationData.identifier.Name.ToString();

            bool isBundle = false;
            for (int b = 0; b < archiveBunldes.Count; ++b)
            {
                BundleInfo info = archiveBunldes[b];
                if (identitier.Contains(info.file.identitier))
                {
                    info.file.identitier = identitier;
                    info.file.location = locationData.location.Path.Value;
                    isBundle = true;
                    break;
                }
            }

            if (!isBundle)
            {
                otherFiles.Add(new LocationIdentifier()
                {
                    location = locationData.location.Path.Value,
                    identitier = identitier
                });
            }
        }
    }

    private void AnalysisDependencies(string rootDirectory)
    {
        string[] dependenciesLines = File.ReadAllLines(Path.Combine(rootDirectory, dependenciesFile.location));

        BundleInfo curBundle = null;
        
        for (int i = 0; i < dependenciesLines.Length; ++i)
        {
            string line = dependenciesLines[i];
            if (line[0] != '\t')
            {
                curBundle = new BundleInfo();
                archiveBunldes.Add(curBundle);
                continue;
            }
            else if (line[0] == '\t' && line[1] != '\t')
            {
                line = line.Replace("\t", "");
                string[] datas = line.Split(": ", StringSplitOptions.RemoveEmptyEntries);
                if (datas[0] == "File")
                {
                    if (curBundle != null) curBundle.file.identitier = datas[1];
                }
            }
            else if (line[0] == '\t' && line[1] == '\t' && line[2] != '\t')
            {
                line = line.Replace("\t", "");
                string[] datas = line.Split(": ", StringSplitOptions.RemoveEmptyEntries);
                if (datas[0] == "Object")
                {
                    if (curBundle != null) curBundle.objects.Add(datas[1]);
                }
                else if (datas[0] == "Dependency")
                {
                    if (curBundle != null) curBundle.dependencies.Add(datas[1]);
                }
            }
        }
    }
}